﻿using CodePlex.TfsPluginSuite.Configuration;
using CodePlex.TfsPluginSuite.Configuration.ForbiddenPatternsPolicy;
using CodePlex.TfsPluginSuite.Helper;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.VersionControl.Server;
using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace CodePlex.TfsPluginSuite.Policies
{
    /// <summary>
    /// Prevents users from checking in files with forbidden filename patterns. 
    /// </summary>
    public class ForbiddenPatternsPolicy : ISubscriber
    {
        public string Name => typeof(ForbiddenPatternsPolicy).FullName; 

        public SubscriberPriority Priority =>  SubscriberPriority.Normal;
        
        public Type[] SubscribedTypes()
        {
            return new Type[1] { typeof(CheckinNotification) };
        }

        public EventNotificationStatus ProcessEvent(IVssRequestContext requestContext,
            NotificationType notificationType,
            object notificationEventArgs,
            out int statusCode,
            out string statusMessage,
            out ExceptionPropertyCollection properties)
        {
            EventNotificationStatus response = EventNotificationStatus.ActionPermitted;
            properties = new ExceptionPropertyCollection();
            statusMessage = String.Empty;
            statusCode = 0;

            var config = TfsPluginSuiteConfigurationManager.Section.ForbiddenPatternsPolicy;

            // don't run this policy if it's disabled in the config file
            if (!config.Enabled) return response;

            // If this is a check-in notification and is a decision point
            if (notificationEventArgs is CheckinNotification && notificationType == NotificationType.DecisionPoint)
            {
                CheckinNotification notification = notificationEventArgs as CheckinNotification;

                // ignore first class branches
                if (notification.CheckinType != CheckinType.Branch)
                {
                    try
                    {
                        using (var tfsHelper = new TfsHelper(requestContext, notification))
                        {
                            // iterate through all the forbidden patterns set in the config
                            foreach (ForbiddenPatternConfigurationElement forbiddenPatternConfigurationElement in config)
                            {
                                //take config types string, delineate, and convert to the enum
                                var changeTypes = forbiddenPatternConfigurationElement.ChangeTypes.Split(',', ';', '|')
                                    .Select(s => (ChangeType)Enum.Parse(typeof(ChangeType), s.Trim()));

                                // check each of the file paths being checked in
                                // getting pendingchanges instead of using SubmittedItems from RequestContext because we need to know the changetype
                                foreach (PendingChange change in tfsHelper.GetPendingChanges())
                                {
                                    var changeType = (ChangeType)change.ChangeEx;

                                    //Only enforce this policy if the pending change is one we watch, 
                                    //bit wise operators are the fastest when we have dozens of forbidden patterns and thousands of pending changes
                                    if (changeTypes.Any(t => (t & changeType) != 0))
                                    {     
                                        if (Regex.IsMatch(change.ServerItem, forbiddenPatternConfigurationElement.RegularExpression))
                                        {
                                            // send the user a friendly message about why this checkin is denied
                                            statusMessage = forbiddenPatternConfigurationElement.Message + Environment.NewLine +
                                                "First match found: " + change.ServerItem;
                                            return EventNotificationStatus.ActionDenied;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        statusCode = -1;
                        statusMessage += e.ToString();
                        requestContext.Status = e;
                    }
                }
            }
            return response;
        }        
    }
}

